import 'dart:convert';
import 'dart:io';

import 'package:cookie_jar/cookie_jar.dart';
import 'package:dio/dio.dart';
import 'package:dio/io.dart';
import 'package:dio_cookie_manager/dio_cookie_manager.dart';

import '../../global/xmm_storage_manager.dart';
import '../error/xmm_error_string.dart';
import '../error/xmm_error_type.dart';
import '../error/xmm_exception.dart';
import 'xmm_Interceptors.dart';
import 'xmm_cancel_token.dart';
import 'xmm_response_data.dart';

class XmmXHttpClient {
  static XmmXHttpClient? _instance;

  static XmmXHttpClient instance() {
    return _instance ?? XmmXHttpClient._internal();
  }

  late Dio _dio;

  XmmXHttpClient._internal() {
    var options = BaseOptions();

    ///连接超时时间为10秒
    options.connectTimeout = const Duration(seconds: 10);

    ///响应超时时间为10秒
    options.receiveTimeout = const Duration(seconds: 10);

    //cookie存储路径
    final storage = FileStorage(XmmStorageManager.temporaryDirectory?.path);

    _dio = Dio(options);
    _dio.interceptors.add(XmmLogInterceptors());
    _dio.interceptors.add(ParamInterceptors());
    _dio.interceptors.add(CookieManager(PersistCookieJar(storage: storage)));
  }

  Future<dynamic> get(String url,
      {Map<String, dynamic>? params,
      Map<String, dynamic>? heads,
      XmmHttpCancelToken? cancelToken}) async {
    XmmResponseData responseData = await getResponse(
      url,
      params: params,
      heads: heads,
      cancelToken: cancelToken,
    );
    if (responseData.success) {
      return responseData.data;
    } else {
      throw XmmBusinessException(responseData.code,
          message: responseData.msg, data: responseData.data);
    }
  }

  Future<XmmResponseData> getResponse(String url,
      {Map<String, dynamic>? params,
      Map<String, dynamic>? heads,
      XmmHttpCancelToken? cancelToken}) async {
    XmmResponseData responseData = await _request(
      url,
      "GET",
      params: params,
      heads: heads,
      cancelToken: cancelToken,
    );
    return responseData;
  }

  Future<dynamic> post(String url,
      {Object? formData,
      Map<String, dynamic>? params,
      Map<String, dynamic>? heads,
      XmmHttpCancelToken? cancelToken}) async {
    XmmResponseData responseData = await postResponse(
      url,
      formData: formData,
      params: params,
      heads: heads,
      cancelToken: cancelToken,
    );
    if (responseData.success) {
      return responseData.data;
    } else {
      throw XmmBusinessException(responseData.code,
          message: responseData.msg, data: responseData.data);
    }
  }

  Future<XmmResponseData> postResponse(String url,
      {Object? formData,
      Map<String, dynamic>? params,
      Map<String, dynamic>? heads,
      XmmHttpCancelToken? cancelToken}) async {
    formData ??= jsonEncode(params);
    XmmResponseData responseData = await _request(
      url,
      "POST",
      formData: formData,
      heads: heads,
      cancelToken: cancelToken,
    );
    return responseData;
  }

  Future<XmmResponseData> _request(String url, String method,
      {Object? formData,
      Map<String, dynamic>? params,
      Map<String, dynamic>? heads,
      XmmHttpCancelToken? cancelToken}) async {
    Options options = Options();
    options.headers = heads;
    options.method = method;
    try {
      cancelToken?.setCancelToken(CancelToken());
      Response response = await _dio.request(url,
          data: formData,
          queryParameters: params,
          options: options,
          cancelToken: cancelToken?.cancelToken);
      XmmResponseData data = _toResult(response);
      return data;
    } catch (e) {
      if (e is DioException) {
        if (e.type == DioExceptionType.receiveTimeout ||
            e.type == DioExceptionType.sendTimeout ||
            e.type == DioExceptionType.connectionTimeout) {
          throw XmmHttpException(XmmErrorType.networkTimeOut, e.message,
              XmmErrorString.networkError);
        } else if (e.type == DioExceptionType.badCertificate) {
          throw XmmHttpException(XmmErrorType.badCertificate, e.message,
              XmmErrorString.networkError);
        } else if (e.type == DioExceptionType.badResponse) {
          throw XmmHttpException(
              XmmErrorType.badResponse, e.message, XmmErrorString.networkError);
        } else if (e.type == DioExceptionType.connectionError) {
          throw XmmHttpException(XmmErrorType.networkTimeOut, e.message,
              XmmErrorString.networkError);
        } else if (e.type == DioExceptionType.cancel) {
          throw XmmHttpException(
              XmmErrorType.cancel, e.message, XmmErrorString.cancelRequest);
        } else {
          throw XmmHttpException(
              XmmErrorType.unknown, e.message, XmmErrorString.requestError);
        }
      } else if (e is SocketException) {
        throw XmmHttpException(XmmErrorType.networkTimeOut, e.message,
            XmmErrorString.networkError);
      }
      rethrow;
    }
  }

  XmmResponseData _toResult(Response response) {
    if (response.statusCode == 200
        && response.data is Map
        && response.data.isNotEmpty) {
      Map<String, dynamic> json = response.data;
      XmmResponseData result = XmmResponseData();
      result.code = json.containsKey('code') ? json['code'] : response.statusCode;
      result.msg = json.containsKey('msg') ? json['msg'] : response.statusMessage;
      result.data = json.containsKey('data') ? json['data'] : response.data;
      return result;
    } else if (response.statusCode == 200) {
      throw XmmHttpException(XmmErrorType.serverNoJson, response.statusMessage,
          XmmErrorString.serverError);
    } else if ((response.statusCode ?? 0) >= 400 &&
        (response.statusCode ?? 0) < 500) {
      throw XmmHttpException(XmmErrorType.server404, response.statusMessage,
          XmmErrorString.serverError);
    } else if ((response.statusCode ?? 0) >= 500 &&
        (response.statusCode ?? 0) < 600) {
      throw XmmHttpException(XmmErrorType.server500, response.statusMessage,
          XmmErrorString.serverError);
    } else {
      throw XmmHttpException(XmmErrorType.unknown, response.statusMessage,
          XmmErrorString.serverError);
    }
  }

  Map<String, dynamic> createParams() {
    return <String, dynamic>{};
  }
}
